#!/usr/bin/python
###############################################################################
#Programmers:                                                                 #
#             Danil Dotsenko, dd@accentsolution.com                           #
#Date: 11/18/2006                                                             #
#Filename: localConf.py                                                       #
#Version: 0.4.2                                                               #
#                                                                             #
#                                                                             #
#       Copyleft 2006 Danil Dotsenko                                          #
#                                                                             #
#    This program is free software; you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation; either version 2 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program; if not, write to the Free Software              #
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA #
#                                                                             #
###############################################################################

'''
This is a wrapper for ConfigParser-of-the-day. It started out as pyhton's
 own ConfigParser. But due to several issues with it (lowercasing sections,
 messing up order of the file) I switched to ConfigObj backend from 
 http://www.voidspace.org.uk/python/configobj.html
 
Jumping from backend to backend usually means changes to the applet, but,
 with this abstraction layer, we only have to change the functions in this file.
The goal is to have the following stable functions:
	cfgObject([filename]) - makes object a ConfigObj, loading settings from file
	load(filename) - loads settings from file. Note. this MAY clean out previous
	contents. If you want to merge, load 2 separate files into dicts and merge.
	save(filename)
	set(section, option, [value]) - puts single pair or dict of values in INI.
	get([section], [option]) - returns dict of whole, section, or option string.
Notes:
	- There is no "multi-homing" i.e. only one config file name is considered
	  main at a time.
	- All errors and issues are hidden. If data is requested from non-existent
	  section - value is None, not an Error
	- One unusual function is delayed_action([action], [argument])
	  When passed WITH arguments (see the function for allowed arguments),
	  it sets an action to be performed later.
	  When passed WITHOUT arguments, it performs the LAST requested action.
	  This is very usefull for situations when a long list of IF's,
	  but you want to delay the save until you are done with all of them.
'''

import os.path
import configobj

class cfgObject:
	def __init__(self, fileName = None):
		"""
		__init__([fileName]) Creates the Config object.
		Without a file name, the config is initialized empty.
		With a file name, the config is populated by the settings from file.
		"""
		self.encoding = 'utf8'
		self.delayed = []
		self.cfgObject = configobj.ConfigObj()
		if fileName:
			self.cfgObject = configobj.ConfigObj(fileName)

	def load(self, fileName):
		"""
		set_file(fileName)
		This should add (or, more likely REPLACE dependent on configobj library)
		the configs from a file to the current instance.
		"""
		if fileName: # safeguarding for None as fileName
			if os.path.exists(fileName):
				self.cfgObject = configobj.ConfigObj(fileName)
				return True
		return False

	def save(self, fileName=None):
		"""
		save([fileName])
		Saves into the requested file, else into path in self.cfgObject.filename
		"""
		if fileName:
			self.cfgObject.filename = fileName
		self.cfgObject.write()

	def get(self,section=None,option=None):
		"""
		get([section], [option]) returns either the contents of option
		or None if option doesn't exist.

		Returns:
		- None if option doesn't exist.
		- dictionary object with all option=value pairs in the section
		    if only section is given.
		- dictionary object with all option=value pairs in the whole file
		    if nothing is given.
		- string object if option and section are given.
		"""
		answer = None
		if not section: # neither section, nor option is given. Return the contents of the whole ini as dictinary type with nested dictionaries for sections.
			answer = self.cfgObject.dict()
			# note! this returns DICTIONATY type
		elif not option: # this means only section is given. Return contents of the section as dictinary type.
			if self.cfgObject.__contains__(section):
				answer = dict(self.cfgObject[section])
				# note! this returns DICTIONARY type
		elif self.cfgObject.__contains__(section) and self.cfgObject[section].__contains__(option):
			answer = self.cfgObject[section][option]
			# note! this usually returns a STRING
		
		# we will be nice and decode the most commonly occuring INI strings back into pure unicode. 
		# this rutine will skip list (array) types and nested dictionaries.
		# if you use them in your settings, decode them manually.
		# Thus, we only decode 1st level Values that are strings, not Section or Option names.
		if type(answer) == type(''): #string
			try:
				answer = str(answer.decode('utf8'))
				# if this works, the underlying string was NOT a Unicode string
				# if it fails, the string was unicode and we need to return it back as such.
			except:
				answer = answer.decode('utf8')
		elif type(answer) == type({}): #dictionary
			for key in answer.keys():
				if type(answer[key]) == type(''): #string
					try:
						answer[key] = str(answer[key].decode('utf8'))
						# if this works, the underlying string was NOT a Unicode string
						# if it fails, the string was unicode and we need to return it back as such.
					except:
						answer[key] = answer[key].decode('utf8')
		
		return answer

	def set(self,section,options,value=None):
		"""
		set(section,options,[value])
		 Unlike ConfigParser's set, we silently create sections and keys.
		 - if option is Dict, value is not needed and we assume that
		   option=value pairs are given and write those to ini object.
		 - if option is String (Unicode), Value is required.
		"""
		
		# ConfigParser does not check what is given to it by Value.
		# It brakes on save and get(value) if it's true python Unicode
		# we'll convert all values to UTF8 format
		# Later, it would be good to find out if user is in utf8 or utf16.
		# We don't really care about those not in unicode locale, as those people probably don't exist anymore :)
		if type(section) == type(u''): # unicode
			try:
				section = section.encode(self.encoding)
			except:
				pass
		
		# this trick makes all function-use cases use the same algorythm.
		if not (type(options) == type({})):
			options = {options:value}

		for pair in options.items():
			key = pair[0]
			value = pair[1]
			if (type(key) == type(u'')) or (type(value) == type(u'')): # unicode
				try:
					key = key.encode(self.encoding)
					value = value.encode(self.encoding)
				except:
					pass
			
			# silently creating sections if needed.
			if not self.cfgObject.__contains__(section):
				self.cfgObject[section]={}
			
			self.cfgObject[section][key]=value

	def delayed_action(self, action=None, argument=None):
		"""
		delayed_action(['save'/'load'/'reload'],[agrument])

		This is a trick used to delay multiple saves or reloads
		followed after repeated usage of cfgObject.

		Call with no arguments to perform the last requested action
		"""
		if action == None and bool(self.delayed):
			if self.delayed[0] == "save":
				self.save(self.delayed[1])
			if self.delayed[0] == "load":
				self.load(self.delayed[1])
			self.delayed = []
		else:
			self.delayed = [action, argument]
#
###################################################################

def test():
	cfg = cfgObject()
	if cfg.load('test.ini'):
		print cfg.get('Instances','Active')

if __name__ == '__main__':
	test()